﻿using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using XingLucifer.Devices.Models;
using XingLucifer.Devices.Protocols;
using XingLucifer.IBase;

namespace XingLucifer.Devices
{
    public class OmronCIP : IDevices
    {
        private CancellationTokenSource _tokenSource;

        private ICommunication _com;
        public OmronCIP(ICommunication com, CancellationTokenSource tokenSource)
        {
            _com = com;
            _tokenSource = tokenSource;
        }

        #region 注册与注销
        private byte[] _registercmd = new byte[28]
        {
            0x65,0x00,
            0x04,0x00,
            0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
            0x00,0x00,0x00,0x00,
            0x01,0x00,
            0x00,0x00,
        };

        private byte[] cancellation = new byte[] { 0x66, 0, 0, 0, 0x71, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
        #endregion

        public void Close() {
            try
            {
                _com.Write(cancellation);
                _com.Close();
            }
            catch (Exception)
            {

            }
        }

        public bool Open()
        {
            if (_com.Open())
            {
                _com.Write(_registercmd);
                Thread.Sleep(20);
                byte[] bytes = _com.Read(64);
                if (bytes.Length == 28)
                {
                    int status = (bytes[8] + (bytes[9] << 8) + (bytes[10] << 16) + (bytes[11] << 24));
                    if (status == 0)
                    {
                        _com.SetProtocol(new TCP_OmronCIP_Protocol(0, bytes.Skip(4).Take(4).ToArray()));
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"CIP注册异常状态码：{status}");
                        return false;
                    }
                }
            }
            return false;
        }

        public Task Read()
        {
            throw new NotImplementedException();
        }

        public TClass ReadClass<TClass>(string label, TClass obj = null, int count = 3) where TClass : class
        {
            if (obj == null) { obj = Activator.CreateInstance<TClass>(); }
            List<byte> list = new List<byte>()
            {
                0x4c
            };
            byte[] bytes_label = GetLabelBytes(label);
            list.Add((byte)(bytes_label.Length / 2));
            list.AddRange(bytes_label);
            list.Add(0x01);
            list.Add(0x00);
            var bytes = _com.SendToRead(list.ToArray(), count);
            if (bytes == null) { return default; }
            int boolSize = 0;
            StructToBytes.FromBytes(obj, bytes.Skip(4).ToArray(), ref boolSize, 0, _com.EndianType);
            return obj;
        }

        public TClass ReadClass<TClass>(int address, TClass obj = null, int count = 3) where TClass : class
        {
            throw new NotImplementedException("未实现该功能");
        }
        public TValue? ReadSingle<TValue>(int address, int length, int count = 3) { throw new NotImplementedException("未实现该功能"); }
        public TValue? ReadSingle<TValue>(string label, int count = 3)
        {
            Type type = typeof(TValue);
            List<byte> bytes = new List<byte>()
            {
                0x4c
            };
            byte[] bytes_label = GetLabelBytes(label);
            bytes.Add((byte)(bytes_label.Length / 2));
            bytes.AddRange(bytes_label);
            bytes.Add(0x01);
            bytes.Add(0x00);
            var list = _com.SendToRead(bytes.ToArray(), count);
            if (list == null) { return default; };
            switch (list[0])
            {
                case 0xC3:
                    return (TValue)StructToBytes.GetValue(type, list.Skip(2).ToArray(), 0, _com.EndianType);
                case 0xD0:
                    object str = Encoding.ASCII.GetString(list.Skip(4).Take(list[2]).ToArray());
                    return (TValue)str;
            }
            return (TValue)StructToBytes.GetValue(type, list.Skip(2).ToArray(), 0, _com.EndianType);
        }


        public List<T> Read<T>(string[] tags, int count = 3)
        {
            Type type = typeof(T);
            if (type.BaseType.Name == "ValueType")
            {
                throw new NullReferenceException("该方法只能用于类的读取！！！");
            }
            var list = CIPRead(tags, count);
            if (list == null) { return default; };
            int label_num = list[0] + (list[1] << 8);
            int size = 0;
            List<T> datas = new List<T>();
            int stat = (label_num * 2 + 2);
            while (stat < list.Length)
            {
                if (list[stat] == 0xCC)
                {
                    stat += 8;
                }
                var obj = Activator.CreateInstance<T>();
                double sizt_count = StructToBytes.FromBytes(obj, list.Skip(stat).ToArray(), ref size, 0, _com.EndianType);
                datas.Add(obj);
                stat += (int)sizt_count;
            }
            return datas;
        }

        public List<object> Read(string[] tags, int count = 3)
        {
            var list = CIPRead(tags, count);
            if (list == null) { return default; };
            int label_num = list[0] + (list[1] << 8);
            List<object> obj = new List<object>();
            int stat = (label_num * 2 + 2);
            while (stat < list.Length)
            {
                if (list[stat] == 0xCC)
                {
                    stat += 4;
                }
                stat += 2;
                switch (list[stat - 2])
                {
                    case 0xC1:
                        obj.Add(StructToBytes.GetValue(typeof(bool), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 2;
                        break;
                    case 0xD1:
                    case 0xC2:
                        obj.Add(StructToBytes.GetValue(typeof(byte), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 2;
                        break;
                    case 0xC3:
                        obj.Add(StructToBytes.GetValue(typeof(short), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 2;
                        break;
                    case 0xC4:
                        obj.Add(StructToBytes.GetValue(typeof(int), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 4;
                        break;
                    case 0xC5:
                        obj.Add(StructToBytes.GetValue(typeof(long), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 8;
                        break;
                    case 0xC7:
                        obj.Add(StructToBytes.GetValue(typeof(ushort), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 2;
                        break;
                    case 0xC8:
                        obj.Add(StructToBytes.GetValue(typeof(uint), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 4;
                        break;
                    case 0xCA:
                        obj.Add(StructToBytes.GetValue(typeof(float), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 4;
                        break;
                    case 0xCB:
                        obj.Add(StructToBytes.GetValue(typeof(double), list.Skip(stat).ToArray(), 0, _com.EndianType));
                        stat += 8;
                        break;
                    case 0xD0:
                        int str_len = list[stat] + (list[stat + 1] << 8);
                        string str_value = Encoding.ASCII.GetString(list.Skip(stat + 2).Take(str_len).ToArray());
                        obj.Add(str_value);
                        stat = stat + str_len + 2;
                        break;
                }
            }
            return obj;
        }

        private byte[] CIPRead(string[] tags, int count = 3)
        {
            ushort offset = 0;
            List<byte> bytes = new List<byte>()
            {
                0x0A,0x02,
                0x20,0x02,0x24,0x01,
                (byte)tags.Length,0x00,//标签数量
            };
            offset = (ushort)(2 + (tags.Length) * 2);
            bytes.AddRange(BitConverter.GetBytes(offset));
            List<byte> lable_bytes = new List<byte>();
            for (int i = 0; i < tags.Length; i++)
            {
                lable_bytes.Add(0x4C);
                byte[] bytes_label = GetLabelBytes(tags[i]);
                lable_bytes.Add((byte)(bytes_label.Length / 2));
                lable_bytes.AddRange(bytes_label);
                lable_bytes.Add(0x01);
                lable_bytes.Add(0x00);
                if (i > 0)
                {
                    bytes.AddRange(BitConverter.GetBytes(offset));
                }
                offset = (ushort)(bytes_label.Length + offset + 4);
            }
            bytes.AddRange(lable_bytes);
            var list = _com.SendToRead(bytes.ToArray(), count);
            if (list == null) { return default; };
            return list;
        }

        public bool WriteClass<TClass>(TClass value, string label, int crc, int count = 3) where TClass : class
        {
            List<byte> bytes = new List<byte>
            {
                0x4D
            };
            byte[] bytes_label = GetLabelBytes(label);
            bytes.Add((byte)(bytes_label.Length / 2));
            bytes.AddRange(bytes_label);
            bytes.Add(0xA0);
            bytes.Add(0x02);
            bytes.Add((byte)crc);
            bytes.Add((byte)(crc >> 8));
            if (value == null) { throw new Exception("不要传递空对象"); }
            int classSize = (int)StructToBytes.GetClassSize(value);
            byte[] array = new byte[classSize];
            int boolSize = 0;
            StructToBytes.ToBytes(value, array, ref boolSize, 0, _com.EndianType);
            bytes.Add(0x01);
            bytes.Add(0);
            bytes.AddRange(array);
            var list = _com.SendToRead(bytes.ToArray(), 3);
            return !(list == null);
        }
        public bool WriteClass<TClass>(TClass value, int address, int count = 3) where TClass : class
        {
            throw new NotImplementedException("未实现该功能");
        }

        public bool WriteSingle(object value, string label, int count = 3)
        { 
            List<byte> bytes = new List<byte>
            {
                0x4D
            };
            byte[] bytes_label = GetLabelBytes(label);
            bytes.Add((byte)(bytes_label.Length / 2));
            bytes.AddRange(bytes_label);
            byte type_cipdata = 0;
            int bytes_length = 0;
            switch (value.GetType().Name)
            {
                case "Int32": type_cipdata = 0xC4; bytes_length = 4; break;
                case "UInt32": type_cipdata = 0xC8; bytes_length = 4; break;
                case "Single": type_cipdata = 0xCA; bytes_length = 4; break;
                case "Int64": type_cipdata = 0xD4; bytes_length = 8; break;
                case "UInt64": type_cipdata = 0xC9; bytes_length = 8; break;
                case "Double": type_cipdata = 0xCB; bytes_length = 8; break;
                case "Int16": type_cipdata = 0xC3; bytes_length = 2; break;
                case "UInt16": type_cipdata = 0xC4; bytes_length = 2; break;
                case "Byte": throw new NotImplementedException("未实现");
                case "Boolean": throw new NotImplementedException("未实现");
                case "String":
                    type_cipdata = 0xD0;
                    bytes_length = value.ToString().Length;
                    bytes_length = bytes_length % 2 != 0 ? bytes_length + 1 : bytes_length;
                    break;
            }
            bytes.Add(type_cipdata);
            bytes.Add(0x00);
            bytes.Add(0x01);
            bytes.Add(0x00);
            byte[] value_bytes = default;
            if (value.GetType().Name == "String")
            {
                value_bytes = new byte[bytes_length + 2];
                value_bytes[0] = (byte)(bytes_length);
                value_bytes[1] = (byte)(bytes_length >> 8);
                var ascii = Encoding.ASCII.GetBytes(value.ToString());
                Array.Copy(ascii, 0, value_bytes, 2, ascii.Length);
            }
            else
            {
                value_bytes = new byte[bytes_length];
                StructToBytes.GetBytes(value, value_bytes, 0, _com.EndianType);
            }
            bytes.AddRange(value_bytes);
            var list = _com.SendToRead(bytes.ToArray(), count);
            return !(list == null);
        }
        public bool WriteSingle(object value, int address, int offset, int length, int count = 3)
        {
            throw new NotImplementedException("未实现该功能");
        }

        #region 辅助方法
        private byte[] GetLabelBytes(string label)
        {
            if (string.IsNullOrEmpty(label))
            {
                throw new Exception("标签为空！！！");
            }
            List<LabelAnalysisModel> labelModels = AnalysisLabel(label);
            List<byte> bytes = new List<byte>();
            foreach (var item in labelModels)
            {
                bytes.AddRange(LabelToBytes(item.Label));
                if (item.IsArray)
                {
                    for (int i = 0; i < item.Index.Length; i++)
                    {
                        if (item.Index[i] > 255)
                        {
                            bytes.Add(0x29);
                            bytes.Add(0);
                            bytes.Add((byte)(item.Index[i] >> 8));
                            bytes.Add((byte)item.Index[i]);
                        }
                        else
                        {
                            bytes.Add(0x28);
                            bytes.Add((byte)item.Index[i]);
                        }
                    }
                }
            }
            return bytes.ToArray();
        }

        private byte[] LabelToBytes(string label)
        {
            var ascii = Encoding.ASCII.GetBytes(label);
            int ascii_length = (ascii.Length % 2 != 0 ? (ascii.Length + 1) : ascii.Length);
            int length = 2 + ascii_length;
            byte[] bytes = new byte[length];
            bytes[0] = 0x91;
            bytes[1] = (byte)(ascii.Length);
            for (int i = 0; i < ascii.Length; i++)
            {
                bytes[i + 2] = (byte)ascii[i];
            }
            return bytes;
        }
        private List<LabelAnalysisModel> AnalysisLabel(string label)
        {
            var list = new List<LabelAnalysisModel>();
            string[] strings = label.Split('.');
            foreach (var item in strings)
            {
                string str = Regex.Match(item, @"(?i)(.*)(?=\[)").Groups[0].Value;
                string str_index = Regex.Match(item, @"(?i)(?<=\[)(.*)(?=\])").Groups[0].Value;
                short[] index_array = default;
                if (str_index.Contains(","))
                {
                    string[] array = str_index.Split(',');
                    index_array = new short[array.Length];
                    for (int i = 0; i < array.Length; i++)
                    {
                        index_array[i] = Convert.ToInt16(array[i]);
                    }
                }
                else if (string.IsNullOrEmpty(str_index))
                {
                    list.Add(new LabelAnalysisModel(item, false, null));
                    continue;
                }
                else
                {
                    index_array = new short[] { Convert.ToInt16(str_index) };

                }
                list.Add(new LabelAnalysisModel(str, true, index_array));
            }
            return list;
        }
        #endregion
    }
}
